package io.polly.ldz.reactor.security;

import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
import io.polly.ldz.reactor.enums.RoleEnum;
import io.polly.ldz.reactor.exception.AuthException;
import io.polly.ldz.reactor.service.UserService;
import io.polly.ldz.reactor.util.JwtTokenUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
import org.springframework.util.AntPathMatcher;
import org.springframework.web.filter.OncePerRequestFilter;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;

/**
 * @Author ldz112@163.com
 * @Classname JwtAuthenticationTokenFilter
 * @Version V1.0
 * @Description authentication
 * @Date 2023/4/24 21:46
 * Copyright (C) 2023 All Rights Reserved
 */

@Slf4j
public class JwtAuthenticationTokenFilter extends OncePerRequestFilter {
    @Value("${jwt.tokenHeader}")
    private String tokenHeader;
    @Value("${jwt.tokenHead}")
    private String tokenHead;
    @Autowired
    private JwtTokenUtil jwtTokenUtil;
    @Autowired
    private UserService userService;

    private String[] excludedEndpoints;

    public JwtAuthenticationTokenFilter(String[] excludedEndpoints) {
        this.excludedEndpoints = excludedEndpoints;
    }

    @Override
    protected boolean shouldNotFilter(HttpServletRequest request) throws ServletException {
        return Arrays.stream(excludedEndpoints)
                .anyMatch(e -> new AntPathMatcher().match(e, request.getServletPath()));
    }

    static Cache<String, io.polly.ldz.reactor.entity.User> userCache =  CacheBuilder.newBuilder().maximumSize(500).expireAfterWrite(24, TimeUnit.HOURS).build();

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {

        // 请求头中获取token信息
        String authHeader = request.getHeader(tokenHeader);

        if(authHeader==null){
            throw new AuthException();
        }
        // 存在token
        if(null != authHeader && authHeader.startsWith(tokenHead)) {
            // 去除字段名称, 获取真正token
            String authToken = authHeader.substring(tokenHead.length());
            // 利用token获取用户名
            String username = jwtTokenUtil.getUserNameFromToken(authToken);
            // token存在用户但未登陆
            // SecurityContextHolder.getContext().getAuthentication() 获取上下文对象中认证信息
            if(null != username) {
                // 自定义数据源获取用户信息
                io.polly.ldz.reactor.entity.User user = null;
                try {
                    user = userCache.get(username,()->userService.getUserByUserName(username));
                } catch (ExecutionException e) {
                    log.error(e.getMessage(),e);
                }

                if (user == null) {
                    throw new AuthException();
                }
                ArrayList<GrantedAuthority> grantedAuthorities = new ArrayList<>();
                grantedAuthorities.add(new SimpleGrantedAuthority(RoleEnum.fromCode(user.getRole()).getRoleName()));

                User authUser = new User(user.getUserName(), user.getPassword(), grantedAuthorities);
                // 验证token是否有效 验证token用户名和存储的用户名是否一致以及是否在有效期内, 重新设置用户对象
                if(jwtTokenUtil.validateToken(authToken, user)) {
                    // 重新将用户信息封装到UsernamePasswordAuthenticationToken
                    UsernamePasswordAuthenticationToken authenticationToken = new
                            UsernamePasswordAuthenticationToken(authUser, null, authUser.getAuthorities());
                    authenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
                    // 将信息存入上下文对象
                    SecurityContextHolder.getContext().setAuthentication(authenticationToken);
                }
            }else{
                throw new AuthException();
            }
        }
        filterChain.doFilter(request,response);
    }
}
